using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Minesweeper.GUI.HUD;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;
using Random = UnityEngine.Random;
using static Minesweeper.GameStatic;

namespace Minesweeper
{
    [RequireComponent(typeof(Tilemap))]
    public class GameGenerator : MonoBehaviour
    {
        public static GameGenerator Instance;
        
        public int width, height, mineCount;
        
        public Tile tileUndiscovered,
            tileEmpty,
            tile1,
            tile2,
            tile3,
            tile4,
            tile5,
            tile6,
            tile7,
            tile8,
            tileExploded,
            tileMark;

        public float explodeDamageRange = 3;
        public float explodeDamageMax = 50;

        public Tilemap Tilemap => GetComponent<Tilemap>();
        public int ClearedGridCount => _discoveredPositions.Count;
        public int MarkedMineCount => _markedPositions.Count;
        [NonSerialized] public int DiggedMineCount;
        public int ExplodedMineCount => _explodedPositions.Count;
        
        private readonly HashSet<Vector2Int> _mines = new();

        private void Awake()
        {
            Instance = this;
            _camera = Camera.main;
            _explodeParticlePrefab = Resources.Load<GameObject>("Minesweeper/Prefabs/Explosion Particle");
        }

        public void Start()
        {
            Generate();
        }
        
        // ReSharper disable once PossibleLossOfFraction
        // 要的就是向下取整
        private void Generate()
        {
            for (var i = 0; i < mineCount; ++i)
            {
                rnd:
                var pos = RandomPos(-width, width, -height, height);
                if (_mines.Contains(pos) || pos is { x: <= 1 and >= -1, y: <= 1 and >= -1 })
                    goto rnd;
                _mines.Add(pos);
            }

            var positions = new Vector3Int[width * height * 4];
            var tile = new TileBase[width * height * 4];
            for (int i = -width, ii = 0; i < width; ++i, ++ii)
            {
                for (int j = -height, jj = 0; j < height; ++j, ++jj)
                {
                    positions[jj * 2 * width + ii] = new Vector3Int(i, j);
                    tile[jj * 2 * width + ii] = tileUndiscovered;
                }
            }
            Tilemap.SetTiles(positions, tile);
            Discover(new Vector2Int(0, 0));
        }
    
        // ReSharper disable PossibleNullReferenceException
        // OnMouseOver事件发生时Camera.main必定不是null，因为此事件的触发依赖于Camera.main
        private void OnMouseOver()
        {
            if (Paused)
                return;
            
            if (Input.GetMouseButtonDown(0))
            {
                var cellPos = Vector2Int.RoundToInt(_camera.ScreenToWorldPoint(Input.mousePosition));
                LeftClick(cellPos);
            }
            else if (Input.GetMouseButtonUp(1))
            {
                var cellPos = Vector2Int.RoundToInt(_camera.ScreenToWorldPoint(Input.mousePosition));
                
                if (_discoveredPositions.Contains(cellPos)) return;
                if (_markedPositions.Contains(cellPos))
                {
                    SetTile(cellPos, tileUndiscovered);
                    _markedPositions.Remove(cellPos);
                }
                else
                {
                    SetTile(cellPos, tileMark);
                    _markedPositions.Add(cellPos);
                }
            }
        }

        private void LeftClick(Vector2Int pos)
        {
            if (_discoveredPositions.Contains(pos))
            {
                var count = GetRemaining(pos);
                var displayCount = new[]
                    {
                        pos + Vector2Int.left,
                        pos + Vector2Int.up,
                        pos + Vector2Int.right,
                        pos + Vector2Int.down,
                        pos + new Vector2Int(-1, 1),
                        pos + Vector2Int.one,
                        pos - Vector2Int.one,
                        pos + new Vector2Int(1, -1)
                    }
                        .Count(x
                            => x.x >= -width && x.y >= -height && x.x < width && x.y < width &&
                               !_discoveredPositions.Contains(x) && _mines.Contains(x));
                if (8 - count.Length != displayCount) return;
                foreach (var i in count)
                    if (!Discover(i))
                        Explode(i);
            }
            else if (_markedPositions.Contains(pos))
            {
                var p = Player.Instance.position;
                if (p.x >= pos.x - 1 && p.x <= pos.x + 1 && p.y >= pos.y - 1 && p.y <= pos.y + 1 && !Player.Instance.Digging)
                    StartCoroutine(Dig(pos));
            }
            else if (!Discover(pos))
                Explode(pos);
        }

        private IEnumerator Dig(Vector2Int pos)
        {
            Player.Instance.StartDigging(pos);
            yield return new WaitForSeconds(5);
            Player.Instance.StopDigging();
            if (_mines.Contains(pos))
            {
                _mines.Remove(pos);
                _markedPositions.Remove(pos);
                DiggedMineCount++;
                ForceDiscover(pos);
            }
            else
            {
                Discover(pos);
                // TODO: Tip balloon "这里根本没有什么地雷！"
            }
        }

        private readonly HashSet<Vector2Int> _dispatchedDiscoverPositionsBuffer = new();
        private readonly List<Vector2Int> _dispatchedDiscoverPositions = new();
        private readonly HashSet<Vector2Int> _discoveredPositions = new();
        private readonly HashSet<Vector2Int> _markedPositions = new();
        private readonly HashSet<Vector2Int> _explodedPositions = new();

        private Camera _camera;

        private GameObject _explodeParticlePrefab;
        
        private void Explode(Vector2Int tilePos)
        {
            _explodedPositions.Add(tilePos);
            _mines.Remove(tilePos);
            ForceDiscover(tilePos);
            SetTile(tilePos, tileExploded);
            Vector3 world = (Vector3Int)tilePos;
            Instantiate(_explodeParticlePrefab, world, Quaternion.identity);

            var dist = Vector2Int.Distance(tilePos, Player.Instance.position);
            if (dist > explodeDamageRange)
                return;
            var dmg = Mathf.RoundToInt(Mathf.Lerp(explodeDamageMax, 0, dist / explodeDamageRange));
            Player.Instance.Damage(dmg);
        }
        
        private void Update()
        {
            if (Paused)
                return;
            
            if (_dispatchedDiscoverPositionsBuffer.Count != 0)
            {
                _dispatchedDiscoverPositions.AddRange(_dispatchedDiscoverPositionsBuffer);
                _dispatchedDiscoverPositionsBuffer.Clear();
            }

            foreach (var t in _dispatchedDiscoverPositions)
                Discover(t);

            _dispatchedDiscoverPositions.Clear();
        }

        private Vector2Int[] GetRemaining(Vector2Int pos)
        {
            return new[]
            {
                pos + Vector2Int.left,
                pos + Vector2Int.up,
                pos + Vector2Int.right,
                pos + Vector2Int.down,
                pos + new Vector2Int(-1, 1),
                pos + Vector2Int.one,
                pos - Vector2Int.one,
                pos + new Vector2Int(1, -1)
            }.Where(x => x.x >= -width && x.y >= -height && x.x < width && x.y < width && 
                         !_markedPositions.Contains(x)).ToArray();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pos"></param>
        /// <returns>点击后是否安全</returns>
        /// <exception cref="Exception">一个周围多于9个雷，不可能抛出</exception>
        public bool Discover(Vector2Int pos)
        {
            if (_mines.Contains(pos))
                return false;

            if (!_discoveredPositions.Add(pos))
                return true;

            var detectingPos = new[]
            {
                pos + Vector2Int.left,
                pos + Vector2Int.up,
                pos + Vector2Int.right,
                pos + Vector2Int.down,
                pos + new Vector2Int(-1, 1),
                pos + Vector2Int.one,
                pos - Vector2Int.one,
                pos + new Vector2Int(1, -1)
            }
                .Where(x
                    => x.x >= -width && x.y >= -height && x.x < width && x.y < width && !_explodedPositions.Contains(x))
                .ToArray();
            var discoverPos = detectingPos
                .Where(p => Vector2Int.Distance(p, Player.Instance.position) <= 5)
                .ToArray(); 
            
            var count = detectingPos.Count(p => _mines.Contains(p));
            SetTile(pos, count switch
            {
                0 => tileEmpty,
                1 => tile1,
                2 => tile2,
                3 => tile3,
                4 => tile4,
                5 => tile5,
                6 => tile6,
                7 => tile7,
                8 => tile8,
                _ => throw new Exception("怎么可能一个格子旁边存在>=9个雷呢？？？")
            });
            if (count != 0) return true;
            _dispatchedDiscoverPositionsBuffer.AddRange(discoverPos);
            return true;
        }

        private void SetTile(Vector2Int pos, Tile t)
        {
            Tilemap.SetTile((Vector3Int)pos, t);
            Minimap.Instance.UpdateTile(pos, t);
        }
        
        public bool ForceDiscover(Vector2Int pos)
        {
            var detectingPos = new[]
                {
                    pos + Vector2Int.left,
                    pos + Vector2Int.up,
                    pos + Vector2Int.right,
                    pos + Vector2Int.down,
                    pos + new Vector2Int(-1, 1),
                    pos + Vector2Int.one,
                    pos - Vector2Int.one,
                    pos + new Vector2Int(1, -1)
                }
                .Where(x
                    => x.x >= -width && x.y >= -height && x.x < width && x.y < width && !_explodedPositions.Contains(x))
                .ToArray();
            var discoverPos = detectingPos
                .Where(p => Vector2Int.Distance(p, Player.Instance.position) <= 5)
                .ToArray(); 
            
            var count = detectingPos.Count(p => _mines.Contains(p));
            SetTile(pos, count switch
            {
                0 => tileEmpty,
                1 => tile1,
                2 => tile2,
                3 => tile3,
                4 => tile4,
                5 => tile5,
                6 => tile6,
                7 => tile7,
                8 => tile8,
                _ => throw new Exception("怎么可能一个格子旁边存在>=9个雷呢？？？")
            });
            if (count == 0)
                _dispatchedDiscoverPositionsBuffer.AddRange(discoverPos);

            foreach (var i in detectingPos
                         .Where(x => _discoveredPositions.Contains(x)))
            {
                ForceDiscoverSub(i);
            }
            return _discoveredPositions.Add(pos);;
        }

        private void ForceDiscoverSub(Vector2Int pos)
        {
            var detectingPos = new[]
                {
                    pos + Vector2Int.left,
                    pos + Vector2Int.up,
                    pos + Vector2Int.right,
                    pos + Vector2Int.down,
                    pos + new Vector2Int(-1, 1),
                    pos + Vector2Int.one,
                    pos - Vector2Int.one,
                    pos + new Vector2Int(1, -1)
                }
                .Where(x
                    => x.x >= -width && x.y >= -height && x.x < width && x.y < width && !_explodedPositions.Contains(x))
                .ToArray();
            var discoverPos = detectingPos
                .Where(p => Vector2Int.Distance(p, Player.Instance.position) <= 5)
                .ToArray(); 
            
            var count = detectingPos.Count(p => _mines.Contains(p));
            SetTile(pos, count switch
            {
                0 => tileEmpty,
                1 => tile1,
                2 => tile2,
                3 => tile3,
                4 => tile4,
                5 => tile5,
                6 => tile6,
                7 => tile7,
                8 => tile8,
                _ => throw new Exception("怎么可能一个格子旁边存在>=9个雷呢？？？")
            });
            if (count == 0)
                _dispatchedDiscoverPositionsBuffer.AddRange(discoverPos);
        }
        
        private static Vector2Int RandomPos(int xmin, int xmax, int ymin, int ymax)
        {
            return new Vector2Int(Random.Range(xmin, xmax), Random.Range(ymin, ymax));
        }
    }
}
